home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Mac Game Programming Gurus / TricksOfTheMacGameProgrammingGurus.iso / More Source / Libraries / SAT 2.3b4 / Demo ƒ / OffscreenToys SAT demo ƒ / OffscreenToysSAT.p < prev    next >
Text File  |  1995-03-30  |  24KB  |  781 lines

  1. {--------- OFFSCREEN TOYS with SAT ---------}
  2. {by Ingemar Ragnemalm 1994}
  3.  
  4. {Offscreen Toys is a nice little demo I made to make a SAT-like demo with complete source}
  5. {code, independent of any libraries (except Apple's). Now, this should be easier to do with}
  6. {SAT, right? Well, partially so, but while adapting it to SAT, I ran into a minor flaw that}
  7. {SAT had (and doesn't have from version 2.1 and up), namely that drawing took place after}
  8. {moving sprites, but before checking for collisions, which didn't look too good in programs}
  9. {where sprites bounce off each other. After fixing this flaw, I would say that the SAT}
  10. {version is indeed a bit better than the independent version. The result is quite a bit faster}
  11. {and with asynch sound.}
  12. {}
  13. {As yet another SAT demo, what does it give us?}
  14. {• If you wonder what it costs to have a real event loop, or good collision handling, this demo}
  15. {shows that pretty well.}
  16. {• Demonstrates a moveable window AND fast mode at the same time, with the precautions}
  17. {that demands when moving the window. (The window mustn't be moved outside the screen,}
  18. {some internal SAT variables – that you otherwise should never care about – must be adjusted,}
  19. {and we must stay word-aligned to make it work in b/w).}
  20. {}
  21. {I don't consider this demo final in any way. Known flaws:}
  22. {• I have made some mistakes in the port-setting. Nothing fatal, I think. FIXED.}
  23. {• I don't protect the user from moving the window outside the screen, which might be fatal}
  24. {when the SAT blitters are turned on. FIXED.}
  25. {• Like in the "real" Offscreen Toys, there is a bug that causes sprites to disappear for a short}
  26. {while, since the position gets negative. No big deal.}
  27. {• The code for the marble should be separated from the main program, to make the code easier}
  28. {to follow.}
  29. {I'll fix those things when I find time and inspiration for it - but you are welcome to do it if}
  30. {you want!}
  31.  
  32. program OffscreenToysSAT;
  33.     uses
  34. {$ifc UNDEFINED THINK_PASCAL}
  35.         Types, QuickDraw, Events, Windows, Dialogs, Fonts, DiskInit, {PackIntf,}
  36.         TextEdit, Traps, {Desk,}
  37.         Devices, Memory, SegLoad, Scrap, ToolUtils, {OSEvents,}
  38.         OSUtils, Menus, Resources, StandardFile, GestaltEqu, Files, Errors, 
  39. {$elsec}
  40.         InterfacesUI, 
  41. {$endc}
  42.         SAT_OT; {Customized version}
  43.  
  44. { --- PART 1: Variables and constants: -----------------------------------------}
  45.  
  46.     const
  47.         kAppleID = 128;
  48.         kFileID = 129;
  49.         kMBarHeight = 20;    { We assume 20 pixels menu bar for window sizing and dragging.}
  50.  
  51.         kWindID = 128;            { Window resource ID }
  52.         kAboutAlertID = 128;    { Alert resource ID }
  53.  
  54.         kSpriteNumber = 5;        { Number of moving objects }
  55.     var
  56.         gColorQDFlag: Boolean;        {True if 32-bit QD exists. If not, we run everything in b/w.}
  57.         gHasWNE: Boolean;        {True if we can use WaitNextEvent}
  58.         gSoundFlag: Boolean;        {True if we want sound.}
  59.         gFast: Boolean;
  60.  
  61.         gWhoa: Boolean;            {True when we want to quit}
  62.         gCollisionFlag: Boolean;    {Collisions or not?}
  63.  
  64. {Menu handles}
  65.         appleMenu, fileMenu: MenuHandle;
  66.  
  67. {The window we'll be using}
  68.         gWind: WindowPtr;
  69.  
  70. {Our two offscreens:}
  71. {offScreen, backScreen: GrafPtr;}
  72.  
  73. {A cicn loded as a face}
  74.         gCicn: FacePtr;
  75.         kgck: Handle;
  76.  
  77. {Sprite information. In real games, I prefer making a linked list of records, like I do in}
  78. {SAT, and a lot more information for each, but here we want it *simple*.}
  79. {- position: The positions in local coordinates for the window}
  80. {- fixedPos: 16 times position, which gives us fixed-point numbers}
  81. {- speed: Speed vectors that is added to fixedPos for every frame}
  82. {- r: Rectangles used in drawing, for remembering what part of the screen to update}
  83. {position, fixedPos: array[1..kSpriteNumber] of Point;}
  84. {speed: array[1..kSpriteNumber] of Point;}
  85. {r: array[1..kSpriteNumber] of Rect;}
  86. {}
  87. {…but, since this now uses SAT, the linked list is built-in, so all the variables above are in}
  88. {the sprite record!}
  89.  
  90. {A longint that shows how big the "bowl" is (assumes circular, not oval, bowl!):}
  91.  
  92.         gBowlSize: Longint;
  93.  
  94. { --- PART 2: Various general, reuseable routines, mostly glue: ---------------------}
  95.  
  96. {BailOut: Emergency exit. We go here on most errors. Real programs report what the}
  97. {problem is. You may wish to put a breakpoint in BailOut when debugging.}
  98.  
  99.     procedure BailOut;
  100.     begin
  101.         SysBeep(1); {Minimal error message. Use alert in real programs.}
  102.         halt;
  103.     end;
  104.  
  105. {Several functions deleted since SAT handles what they do.}
  106.  
  107. { --- PART 3: Application specific routines: ---------------------------------}
  108.  
  109. {mouse clicks, keydowns, background tasks and update events: This is where all}
  110. {the action is. :-) I include some empty procedures for you to fill in if you want to}
  111. {use this demo as application shell.}
  112.  
  113. {Mouse click in window content}
  114.  
  115.     procedure DoMouse (where: Point; modifiers: Longint);
  116.     begin
  117.     end;
  118.  
  119. {Keydown.}
  120.  
  121.     procedure DoKey (theKey: Char; modifiers: Longint);
  122.     begin
  123.     end;
  124.  
  125.     const
  126.         kWallBounce = 7;                            {1/10-ths of speed kept after wallbounce}
  127.         kBallDiameterSquared = 32 * 32;            {Diameter 32, squared}
  128.  
  129.  
  130. {A rather boring subroutine that moves the sprites i and j away from each other.}
  131. {I almost didn't want to put this in, making the demo unnecessarily big, but I wanted}
  132. {decent collisions. If anyone can suggest a better (simpler) collision handling for circular}
  133. {objects, I'd be happy to put it in.}
  134. {I use a line drawing algorithm for it. I'm sure there are better ways. This is of questionable}
  135. {value as reuseable code - depends on your application.}
  136.     procedure Separate (i, j: SpritePtr);
  137.         var
  138.             initVector, nowVector: Point;
  139.             absH, absV: integer;
  140.             moveH, moveV: integer;
  141.             frac: integer;
  142. {Normal signum function (which I don't think is in the libs)}
  143.         function Sgn (x: integer): integer;
  144.         begin
  145.             if x > 0 then
  146.                 Sgn := 1
  147.             else if x < 0 then
  148.                 Sgn := -1
  149.             else
  150.                 Sgn := 0;
  151.         end;
  152.  
  153.     begin {Separate}
  154.         frac := 0;
  155.         initVector.h := i^.position.h - j^.position.h;
  156.         initVector.v := i^.position.v - j^.position.v;
  157.         absH := abs(initVector.h);
  158.         absV := abs(initVector.v);
  159.         moveH := Sgn(initVector.h);
  160.         moveV := Sgn(initVector.v);
  161.         if moveH = 0 then
  162.             if moveV = 0 then
  163.                 moveV := 1;
  164.         repeat
  165.             if absH > absV then
  166.                 begin
  167.                     i^.position.h := i^.position.h + moveH;
  168.                     j^.position.h := j^.position.h - moveH;
  169.                     frac := frac + absV;
  170.                     if frac > absH then
  171.                         begin
  172.                             i^.position.v := i^.position.v + moveV;
  173.                             j^.position.v := j^.position.v - moveV;
  174.                             frac := frac - absH;
  175.                         end
  176.                 end
  177.             else
  178.                 begin
  179.                     i^.position.v := i^.position.v + moveV;
  180.                     j^.position.v := j^.position.v - moveV;
  181.                     frac := frac + absH;
  182.                     if frac > absV then
  183.                         begin
  184.                             i^.position.h := i^.position.h + moveH;
  185.                             j^.position.h := j^.position.h - moveH;
  186.                             frac := frac - absV;
  187.                         end
  188.                 end;
  189.             nowVector.h := i^.position.h - j^.position.h;
  190.             nowVector.v := i^.position.v - j^.position.v;
  191.         until longint(nowVector.h) * nowVector.h + longint(nowVector.v) * nowVector.v > kBallDiameterSquared;
  192.         i^.fixedPos.h := BSL(i^.position.h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
  193.         i^.fixedPos.v := BSL(i^.position.v, 4);
  194.  
  195.         j^.fixedPos.h := BSL(j^.position.h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
  196.         j^.fixedPos.v := BSL(j^.position.v, 4);
  197.  
  198.     end;{Separate}
  199.  
  200. {Split a vector (v1) into one component parallell to another vector (direction) and one}
  201. {orthogonal to it.}
  202.     procedure SplitVector (v1, direction: Point; var parallell, normal: Point);
  203.         var
  204.             l2, v1pr: Longint;
  205.     begin
  206. {parallell := direction * (v1 DOT direction) /|direction|**2}
  207. {normal := v1 - parallell}
  208.  
  209.         l2 := direction.h * direction.h + direction.v * direction.v; {Squared length of "direction"}
  210.         v1pr := v1.h * direction.h + v1.v * direction.v; {Scalar product}
  211.  
  212.         parallell.h := direction.h * v1pr div l2;
  213.         parallell.v := direction.v * v1pr div l2;
  214.         normal.h := v1.h - parallell.h;
  215.         normal.v := v1.v - parallell.v;
  216.     end; {SplitVector}
  217.  
  218.     procedure HandleMarble (i: SpritePtr);
  219.         var
  220.             vector: Point;
  221.     begin
  222. {Modify fixed-point position by speed}
  223.         i^.fixedPos.h := i^.fixedPos.h + i^.speed.h;
  224.         i^.fixedPos.v := i^.fixedPos.v + i^.speed.v;
  225.  
  226. {Make position by shifting away the 4 binary "decimals"}
  227.         if i^.fixedPos.h >= 0 then
  228.             i^.position.h := BSR(i^.fixedPos.h, 4)
  229.         else
  230.             i^.position.h := BitOr(BSR(i^.fixedPos.h, 4), $f000);
  231.         if i^.fixedPos.v >= 0 then
  232.             i^.position.v := BSR(i^.fixedPos.v, 4)
  233.         else
  234.             i^.position.v := BitOr(BSR(i^.fixedPos.v, 4), $f000);
  235.  
  236. {Outside the allowed area?}
  237.         if i^.position.h < 0 then
  238.             begin
  239.                 i^.speed.h := abs(i^.speed.h) * kWallBounce div 10 + 1;
  240. {     i^.position.h := 0;}
  241.             end;
  242.         if i^.position.v < 0 then
  243.             begin
  244.                 i^.speed.v := abs(i^.speed.v) * kWallBounce div 10 + 1;
  245. { i^.position.v := 0; }
  246.             end;
  247.         if i^.position.h + gCicn^.iconMask.bounds.right > gSAT.offScreen.port^.portRect.right then
  248.             begin
  249.                 i^.speed.h := -abs(i^.speed.h) * kWallBounce div 10 - 1;
  250. { i^.position.h := gSAT.offScreen^.portRect.right - gCicn^.iconMask.bounds.right; }
  251.             end;
  252.         if i^.position.v + gCicn^.iconMask.bounds.bottom > gSAT.offScreen.port^.portRect.bottom then
  253.             begin
  254.                 i^.speed.v := -abs(i^.speed.v) * kWallBounce div 10 - 1;
  255. { i^.position.v := gSAT.offScreen^.portRect.bottom - gCicn^.iconMask.bounds.bottom; }
  256.             end;
  257.  
  258. {Are we in the bowl? If we are, accelerate towards the center.}
  259.         vector.h := i^.position.h + 16 - BSR(gSAT.offScreen.port^.portRect.right, 1);
  260.         vector.v := i^.position.v + 16 - BSR(gSAT.offScreen.port^.portRect.bottom, 1);
  261.         if (vector.h * vector.h + vector.v * vector.v) < gBowlSize then
  262.             begin
  263.                 i^.speed.h := i^.speed.h - vector.h div 2;
  264.                 i^.speed.v := i^.speed.v - vector.v div 2;
  265.             end;
  266.     end; {position/speed loop}
  267.  
  268.     procedure HitMarble (i, j: SpritePtr);
  269.         var
  270.             vector: Point;
  271.             squaredLength: Longint;
  272.             p1, p2, n1, n2, tmpSpeed: Point;
  273.     begin
  274.         if not gCollisionFlag then
  275.             exit(HitMarble);
  276.  
  277. {Find the vector between them}
  278.         vector.h := i^.position.h - j^.position.h;
  279.         vector.v := i^.position.v - j^.position.v;
  280.         squaredLength := longint(vector.h) * vector.h + longint(vector.v) * vector.v;
  281. {If it is shorter than the diameter of a ball…}
  282.         if squaredLength < kBallDiameterSquared then
  283.             begin
  284. {Move them away from each other}
  285.                 Separate(i, j);
  286.  
  287. {if false then}
  288.                 begin
  289. {Swap the speed components that are parallell to "vector" (this allows for "touches", very}
  290. {nice and realistic bounces)}
  291.                     SplitVector(i^.speed, vector, p1, n1);
  292.                     SplitVector(j^.speed, vector, p2, n2);
  293.  
  294.                     j^.speed.h := p1.h + n2.h;
  295.                     j^.speed.v := p1.v + n2.v;
  296.  
  297.                     i^.speed.h := p2.h + n1.h;
  298.                     i^.speed.v := p2.v + n1.v;
  299.                 end;
  300.  
  301. {Old Offscreen Toys just swapped the speed, as commented out below. This is not as realistic.}
  302. {tmpSpeed := i^.speed;}
  303. {i^.speed := j^.speed;}
  304. {j^.speed := tmpSpeed;}
  305.  
  306. {Play a sound. SAT is good at playing sounds!}
  307.                 if gSoundFlag then
  308.                     SATSoundPlay(kgck, 1, false);
  309.  
  310.             end;
  311.     end; {collision loop}
  312.  
  313.     procedure SetupMarble (i: SpritePtr);
  314.     begin
  315.         i^.fixedPos.h := BSL(i^.position.h, 4);
  316.         i^.fixedPos.v := BSL(i^.position.v, 4);
  317.         i^.speed.h := Random mod 32;
  318.         i^.speed.v := Random mod 32;
  319.         i^.task := @HandleMarble;
  320.         i^.hitTask := @HitMarble;
  321.         i^.face := gCicn;
  322.         SetRect(i^.hotRect, 0, 0, 32, 32);
  323.     end;
  324.  
  325. {A static variable only used in DrawBackground}
  326.     var
  327.         thePat: PixPatHandle;
  328.  
  329.     procedure DrawBackground;
  330.         const
  331.             patID = 128;
  332.         var
  333.             r: Rect;
  334.             mycolorFlag: Boolean;
  335.  
  336. {A little routine for setting the forecolor with a single line.}
  337.         procedure OTForeColor (red, green, blue: integer);
  338.             var
  339.                 theColor: RGBColor;
  340.         begin
  341.             theColor.red := red;
  342.             theColor.green := green;
  343.             theColor.blue := blue;
  344.             RGBForeColor(theColor);
  345.         end;
  346.  
  347.         var
  348.             savePort: SATPort;
  349.  
  350.     begin {DrawBackground}
  351.         SATGetPort(savePort);
  352.  
  353.         SATSetPortBackScreen;
  354.  
  355. {Do some drawing in backScreen. First, we paint a pattern (using a 'ppat' resource):}
  356.  
  357. {For drawing the background, let's make a local flag that tells us if we shold draw}
  358. {b/w patterns or color ones.}
  359.         if gColorQDFlag then
  360.             mycolorFlag := gSAT.initDepth > 1
  361.         else
  362.             mycolorFlag := false;
  363.  
  364.         if mycolorFlag then
  365.             begin
  366.                 if thePat = nil then
  367.                     thePat := GetPixPat(patID);
  368.                 PenPixPat(thePat)
  369.             end
  370.         else
  371.             begin
  372.                 if thePat = nil then
  373.                     thePat := PixPatHandle(GetResource('ppat', patID));
  374.                 PenPat(thePat^^.pat1Data);
  375.             end;
  376.         PaintRect(gSAT.backScreen.port^.portRect);
  377.         PenNormal;
  378.  
  379. {Then we draw some circles.}
  380.  
  381.         r := gSAT.backScreen.port^.portRect;
  382.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  383.         gBowlSize := longint(r.right - r.left) * (r.right - r.left) div 4;        {Tells how big the "bowl" is!}
  384.         if mycolorFlag then
  385.             begin
  386.                 OTForeColor(-10000, -10000, -10000);
  387.                 PaintOval(r);
  388.             end
  389.         else
  390. {$IFC UNDEFINED THINK_PASCAL}
  391.             FillOval(r, qd.ltGray);
  392. {$ELSEC}
  393.         FillOval(r, ltGray);
  394. {$ENDC}
  395.  
  396.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  397.         if mycolorFlag then
  398.             begin
  399.                 OTForeColor(-25000, -25000, -25000);
  400.                 PaintOval(r);
  401.             end
  402.         else
  403. {$IFC UNDEFINED THINK_PASCAL}
  404.             FillOval(r, qd.gray);
  405. {$ELSEC}
  406.         FillOval(r, gray);
  407. {$ENDC}
  408.  
  409.         InsetRect(r, (r.right - r.left) div 6, (r.bottom - r.top) div 6);
  410.         if mycolorFlag then
  411.             begin
  412.                 OTForeColor(20000, 20000, 20000);
  413.                 PaintOval(r);
  414.             end
  415.         else
  416. {$IFC UNDEFINED THINK_PASCAL}
  417.             FillOval(r, qd.dkGray);
  418. {$ELSEC}
  419.         FillOval(r, dkGray);
  420. {$ENDC}
  421.  
  422.         InsetRect(r, (r.right - r.left) div 5, (r.bottom - r.top) div 5);
  423.         if gSAT.colorFlag then
  424.             OTForeColor(0, 0, 0);
  425.         PaintOval(r);
  426. {         InsetRect(r, (r.right - r.left) div 5, (r.bottom - r.top) div 5);}
  427. {        if mycolorFlag then}
  428. {            begin}
  429. {                OTForeColor(0, 0, 0);}
  430. {                PaintOval(r);}
  431. {            end}
  432. {        else}
  433. {            FillOval(r, black); }
  434.  
  435.         SATSetPortOffScreen;
  436.         CopyBits(gSAT.backScreen.port^.portBits, gSAT.offScreen.port^.portBits, gSAT.backScreen.port^.portRect, gSAT.backScreen.port^.portRect, srcCopy, nil);
  437.  
  438.         SATSetPort(savePort);
  439.     end; {DrawBackground}
  440.  
  441. {DoBackground: repeating tasks - this is called repeatedly, after every event we get.}
  442.  
  443. {Note: If you are making a really Mac-friendly program, this is where you should drive}
  444. {the animation. However, it is hard to get high framerate then, since other programs}
  445. {(the Finder included) will process events which will make it less smooth.}
  446.  
  447.     procedure DoBackground;
  448.         var
  449.             tmpRect: Rect;
  450.             i, j: integer;
  451.             vector: Point;
  452.             saveGD: GDHandle;
  453.             savePort: GrafPtr;
  454.             tmpSpeed: Point;
  455.     begin {DoBackground}
  456.  
  457.         SATRun(gFast); {Eller konfigurerbart?}
  458.  
  459.     end; {DoBackground}
  460.  
  461.  
  462. {DoUpdate: handle update events, in this case by copying offScreen to the screen (gWind).}
  463.  
  464. {Note to beginners: A program without update events processing is not a real Mac program!}
  465. {All drawing you do must reach the update event handler in some way, or it might be lost,}
  466. {or worse, partially erased, which is really ugly.}
  467.  
  468.     procedure DoUpdate;
  469.         var
  470.             saveGD: GDHandle;
  471.             savePort: GrafPtr;
  472.     begin
  473.         if SATDepthChangeTest then
  474.             DrawBackground;
  475.         BeginUpdate(gWind);
  476.         SATRedraw;
  477.         EndUpdate(gWind);
  478.     end;
  479.  
  480. {DoAppleMenu and DoFileMenu: handle menu selections}
  481.  
  482.     procedure DoAppleMenu (item: integer);
  483.         var
  484.             str: Str255;
  485.             h: Handle;
  486.             savePort: SATPort;
  487.             ignore: integer;
  488.     begin
  489.         if item = 1 then
  490.             begin
  491.                 if Alert(kAboutAlertID, nil) = 1 then
  492.                     ; {Ignore result}
  493.             end
  494.         else
  495. {Apple menu other than "About": Code from TransSkel}
  496.             begin
  497.                 SATGetPort(savePort);
  498.                 GetMenuItemText(appleMenu, item, str);                    {Old intf: GetItem}
  499.                 SetResLoad(false);
  500.                 h := GetNamedResource('DRVR', str);
  501.                 SetResLoad(true);
  502.                 if h <> nil then
  503.                     begin
  504.                         ReserveMem(GetResourceSizeOnDisk(h) + $1000);    {Old intf: ResrvMem, SizeResource}
  505.                         ignore := OpenDeskAcc(str);
  506.                     end;
  507.                 SATSetPort(savePort);
  508.             end;
  509.     end; {DoAppleMenu}
  510.  
  511.     procedure DoFileMenu (item: integer);
  512.         var
  513.             start, finish, frames: Longint;
  514.             fpsStr: Str255;
  515.     begin
  516.         case item of
  517.             1:
  518. {Run animation without event processing until the user clicks the mouse}
  519. {Note: This runs the animation at maximum speed. In real programs, we}
  520. {must limit the speed with the system clock, e.g. inspect TickCount.}
  521.                 begin
  522.                     start := TickCount;
  523.                     frames := 0;
  524.                     while not Button do
  525.                         begin
  526.                             DoBackground;
  527.                             frames := frames + 1;
  528.                         end;
  529.                     finish := TickCount;
  530.                     NumToString(frames * 60 div (finish - start), fpsStr);
  531.                     SATReportStr(stringof(fpsStr, ' frames/second'));
  532. {ParamText(fpsStr, ' frames/second', '', '');}
  533. {if Alert(129, nil) = 1 then}
  534.                 end;
  535.             2: 
  536.                 begin
  537.                     gCollisionFlag := not gCollisionFlag;
  538.                     CheckItem(fileMenu, 2, gCollisionFlag);
  539.                 end;
  540.             3: 
  541.                 begin
  542.                     gFast := not gFast;
  543.                     CheckItem(fileMenu, 3, gFast);
  544.                 end;
  545.             4: 
  546.                 begin
  547.                     gSoundFlag := not gSoundFlag;
  548.                     CheckItem(fileMenu, 4, gSoundFlag);
  549.                 end;
  550. {Set the flag that tells the program to quit.}
  551.             6: 
  552.                 gWhoa := true;
  553.         end; {case}
  554.     end; {DoFileMenu}
  555.  
  556. { --- PART 4: Event processing: -----------------------------------------}
  557.  
  558. {MenuSelection: Menu selection by mouse or command-key:}
  559.  
  560.     procedure MenuSelection (whatSelection: longInt);
  561.     begin
  562.         case HiWord(whatSelection) of
  563.             kAppleID: 
  564.                 DoAppleMenu(LoWord(whatSelection));
  565.             kFileID: 
  566.                 DoFileMenu(LoWord(whatSelection));
  567.         end; {case}
  568.         HiLiteMenu(0);
  569.     end;
  570.  
  571. {MainLoop: get and process events. This is the boring standard part of all programs. I prefer}
  572. {using TransSkel to get rid of it. I don't here since I want this code to be stand-alone.}
  573.  
  574.     procedure MainLoop;
  575.         const
  576.             kSleep = 5; {Real programs may modify the sleep time depending on whether or not they are in the front}
  577.         var
  578.             hasEvent: Boolean;
  579.             theEvent: EventRecord;
  580.             theKey: Char;
  581.             whatSelection: Longint;
  582.             whichPart: integer;
  583.             whichWindow: WindowPtr;
  584.             r: rect;
  585.             p: Point;
  586.     begin
  587. {Get the next event. Use WaitNextEvent if possible.}
  588.         if gHasWNE then
  589.             hasEvent := WaitNextEvent(everyEvent, theEvent, kSleep, nil)
  590.         else
  591.             begin
  592.                 SystemTask;
  593.                 hasEvent := GetNextEvent(everyEvent, theEvent);
  594.             end;
  595.  
  596. {OK, so what happened then?}
  597.         if hasEvent then
  598.             case theEvent.what of
  599.                 mouseDown: 
  600.                     begin
  601.                         whichPart := FindWindow(theEvent.where, whichWindow);
  602.                         case whichPart of
  603.                             inMenuBar: 
  604.                                 begin
  605.                                     whatSelection := MenuSelect(theEvent.where);
  606.                                     MenuSelection(whatSelection);
  607.                                 end;
  608.                             inSysWindow: 
  609.                                 SystemClick(theEvent, whichWindow);
  610.                             inGoAway: 
  611.                                 if (TrackGoAway(whichWindow, theEvent.where)) then
  612.                                     gWhoa := true;
  613.                             inDrag: 
  614.                                 begin
  615.                                     if (whichWindow <> FrontWindow) and (BitAnd(theEvent.modifiers, cmdKey) = 0) then
  616.                                         SelectWindow(whichWindow);
  617.                                     r := gSAT.bounds;            {How big is the screen?}
  618. {Was: screenBits.bounds;{How big is the screen? (Note: Don't use screenBits for other things: it isn't a valid BitMap any more!)}
  619.                                     r.top := r.top + kMBarHeight;        { Skip down past menu bar    }
  620.  
  621.                                     InsetRect(r, 4, 4);
  622.  
  623. { LIMIT THE DRAGGING so no part of the window can get outside the screen! Cut down on r}
  624. {depending on where the click is? This is necessary if we use "fast mode", that is if we do}
  625. {SATRun(true) or any other operation with custom blitters.}
  626.                                     SetPort(whichWindow);
  627.                                     p := theEvent.where;
  628.                                     GlobalToLocal(p);
  629.                                     r.bottom := r.bottom - (whichWindow^.portRect.bottom - p.v);
  630.                                     r.left := r.left + p.h;
  631.                                     r.right := r.right - (whichWindow^.portRect.right - p.h);
  632.  
  633.                                     DragWindow(whichWindow, theEvent.where, r);
  634.  
  635.                                     if whichWindow = gSAT.wind.port then
  636.                                         SATWindMoved;
  637.  
  638.                                 end;
  639.                             inGrow: 
  640.                                 ;  {Ignored - we don't resize}
  641.                             inContent: 
  642.                                 if (whichWindow <> FrontWindow) then
  643.                                     SelectWindow(whichWindow)
  644.                                 else
  645.                                     DoMouse(theEvent.where, theEvent.modifiers); {Go to application-specific mouse down handling}
  646.                         end; {case whichPart}
  647.                     end; {mouseDown}
  648.                 keyDown, autoKey: 
  649.                     begin
  650.                         theKey := char(BitAnd(theEvent.message, charCodeMask));
  651.                         if (BitAnd(theEvent.modifiers, cmdKey) <> 0) then
  652.                             MenuSelection(MenuKey(theKey))
  653.                         else
  654.                             DoKey(theKey, theEvent.modifiers);
  655.                     end;
  656.                 updateEvt:
  657. {There's only one window to bother with here, but let's make sure that's the one the Mac wants to update.}
  658.                     if WindowPtr(theEvent.message) = gWind then
  659.                         DoUpdate;
  660. {Handle disk inserts like TransSkel.}
  661.                 diskEvt: 
  662.                     if (HiWord(theEvent.message) <> noErr) then
  663.                         begin
  664.                             DILoad;
  665.                             if DIBadMount(Point($00400040), theEvent.message) = 0 then
  666.                                 ;
  667.                             DIUnload;
  668.                         end; {diskEvt}
  669.                 otherwise {Other events are ignored}
  670.             end; {case}
  671.  
  672.         DoBackground;
  673.     end;
  674.  
  675. { --- PART 5: Initializations: -----------------------------------------}
  676.  
  677. {OTInit: Initialize global flags, menus and window}
  678.  
  679.     procedure OTInit;
  680.         const
  681. {Trap numbers}
  682.             _WaitNextEvent = $A860;
  683.             _GetCIcon = $AA1E; {E.g. any Color QuickDraw routine}
  684.             k32bQD = $AB1D;
  685.             _SndPlay = $A805;
  686.     begin
  687. {In case this isn't Think Pascal we have to make the standard inits ourselves.}
  688. {$IFC UNDEFINED THINK_PASCAL}
  689.         SATInitToolbox;
  690. {$ENDC}
  691.  
  692.         gHasWNE := SATTrapAvailable(_WaitNextEvent);
  693.         gColorQDFlag := SATTrapAvailable(k32bQD) and SATTrapAvailable(_GetCIcon); {???}
  694.         gWhoa := false;
  695.         gCollisionFlag := false;
  696.  
  697. {gSoundFlag := TrapAvailable(_SndPlay); – Let SAT decide if we CAN or not!}
  698.         gSoundFlag := true;
  699.  
  700. {What more should I check for? Check with Gestalt instead?}
  701.  
  702. {$IFC UNDEFINED THINK_PASCAL}
  703.         qd.randSeed := TickCount;            {Seed the random number generator - TickCount is good enough.}
  704. {$ELSEC}
  705.         randSeed := TickCount;            {Seed the random number generator - TickCount is good enough.}
  706. {$ENDC}
  707.  
  708. {Get the window, a color window if we are going to use color.}
  709.         if gColorQDFlag then
  710.             gWind := GetNewCWindow(kWindId, nil, WindowPtr(-1))
  711.         else
  712.             gWind := GetNewWindow(kWindId, nil, WindowPtr(-1));
  713.  
  714. {Some menus. We could read these from resources.}
  715.         appleMenu := NewMenu(kAppleID, stringof(char($14)));
  716.         AppendMenu(appleMenu, 'About Offscreen Toys SAT…;(-');
  717.         AppendResMenu(appleMenu, 'DRVR');            {Old intf: AddResMenu}
  718.         InsertMenu(appleMenu, 0);            { put apple menu at end of menu bar }
  719.         fileMenu := NewMenu(kFileID, 'File');
  720.         AppendMenu(fileMenu, 'Try max speed;Collisions;Use SAT blitters;Use sound;(-;Quit/Q');
  721.         InsertMenu(fileMenu, 0);            { put file menu at end of menu bar }
  722.         DrawMenuBar;
  723.         CheckItem(fileMenu, 4, gSoundFlag);
  724.     end;
  725.  
  726. {OTOffscreensInit: Initialize offscreen grafports (worlds) and draw in them.}
  727.  
  728.     procedure OTOffscreensInit;
  729.         var
  730.             savePort: SATPort;
  731.             r: Rect;
  732.             i: integer;
  733.             sp: SpritePtr;
  734.     begin {OTOffscreensInit}
  735.         SATGetPort(savePort);
  736.  
  737.         SATConfigure(false, kNoSort, kForwardOneCollision, 32); {Only call *one* hitTask, not both.}
  738. {r := gWind^.portRect;}
  739. {OffsetRect(r, -r.left, -r.top);}
  740.         SATCustomInit(0, 0, gWind^.portRect, gWind, nil, false, false, false, true, false);
  741.  
  742.         DrawBackground;
  743.  
  744. {Done drawing!}
  745. {For your own hacks, consider using a PICT resource and use GetPicture and DrawPicture to draw the}
  746. {background. Note that you'll need both a color and a b/w picture if you want it to look good in b/w.}
  747.  
  748.         SATSetPort(savePort);
  749.  
  750. {Get the cicn resource}
  751. {Note: You can, of course, use several cicns and switch between.}
  752.         gCicn := SATGetFace(128);
  753.         kgck := SATGetNamedSound('Kgck');
  754.  
  755. {Initialize the sprites:}
  756.  
  757.         for i := 1 to kSpriteNumber do
  758.             sp := SATNewSprite(1, SATRand(gSAT.offScreen.port^.portRect.right - 32), (i - 1) * (gSAT.offScreen.port^.portRect.bottom - 32) div 5 + SATRand((gSAT.offScreen.port^.portRect.bottom - 32) div 5), @SetupMarble);
  759.  
  760.         if SATSoundInitChannels(2) < 2 then
  761.             SysBeep(1);
  762.     end;
  763.  
  764. { --- MAIN PROGRAM BODY: -----------------------------------------}
  765.  
  766. begin
  767.     OTInit;                    {General initializations}
  768.     OTOffscreensInit;        {Set up the offscreen grafports}
  769.     InitCursor;                {Set the cursor to arrow in case it isn't.}
  770.     SATSetPortScreen;    {The front window is a good port to use.}
  771.  
  772. {Run until quit or click in the close box.}
  773.     repeat
  774.         SATSetPortScreen;    {The front window is a good port to use.}
  775.         MainLoop;
  776.     until gWhoa;
  777.  
  778. {No cleanup is necessary here.}
  779. {We could DisposeGWorld, but that isn't necessary when we are quitting.}
  780.     SATSoundShutup;
  781. end.